/*
 * Copyright (c) 2023, LXMS (lxinet.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.lxinet.lxms.generator.utils;

import cn.lxinet.lxms.generator.Config;
import cn.lxinet.lxms.generator.domain.ColumnEntity;
import cn.lxinet.lxms.generator.domain.DatabaseColumn;
import cn.lxinet.lxms.generator.domain.GeneratorParams;
import cn.lxinet.lxms.generator.domain.TableEntity;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.*;
import java.util.*;

public class GeneratorUtil {

    /**
     * 生成代码
     *
     * @param generatorParams
     */
    public static void generatorCode(GeneratorParams generatorParams) {
        //参数处理
        TableEntity tableEntity = formatParams(generatorParams);
        //设置velocity资源加载器
        initVelocity();
        //封装模板数据
        VelocityContext context = getVelocityContext(generatorParams, tableEntity);
        //渲染模板
        apply(context, tableEntity, generatorParams);
    }

    private static void apply(VelocityContext context, TableEntity tableEntity, GeneratorParams generatorParams) {
        List<String> templates = getTemplates();
        String path = System.getProperty("user.dir") + File.separator + Config.OUT_MODULE + File.separator;
        File pathDir = new File(path);
        if (!pathDir.exists()){
            pathDir.mkdirs();
        }
        templates.forEach(template -> {
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);
            String fileName = getFileName(template, tableEntity.getUpperClassName(), tableEntity.getLowerClassName(), generatorParams);

            String filePath = path + fileName.substring(0, fileName.lastIndexOf(File.separator));
            String javaFileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1);
            File filePathDir = new File(filePath);
            if (!filePathDir.exists()){
                filePathDir.mkdirs();
            }
            File file = new File(filePath, javaFileName);
            try {
                if (file.exists()){
                    return;
                }
                OutputStream out = new FileOutputStream(file);
                IOUtils.write(sw.toString(), out, "UTF-8");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 使用自定义模板
     *
     * @return
     */
    private static List<String> getTemplates() {
        List<String> templates = new ArrayList<>();
        templates.add("template/Mapper.java.vm");
        templates.add("template/Service.java.vm");
        templates.add("template/Controller.java.vm");
        templates.add("template/Entity.java.vm" );
        templates.add("template/Vo.java.vm");
        templates.add("template/CreateDto.java.vm" );
        templates.add("template/UpdateDto.java.vm" );
        templates.add("template/PageDto.java.vm" );
        templates.add("template/Convert.java.vm" );
        templates.add("template/index.vue.vm" );
        templates.add("template/edit.vue.vm" );
        return templates;
    }


    private static String getPackagePath(GeneratorParams generatorParams) {
        //配置信息
        Configuration config = getConfig();
        String packageName = StringUtils.isNotBlank(generatorParams.getPackageName())
                ? generatorParams.getPackageName()
                : config.getString("package");
        String moduleName = StringUtils.isNotBlank(generatorParams.getModuleName())
                ? generatorParams.getModuleName()
                : config.getString("moduleName");
        String packagePath = "src" + File.separator + "main" + File.separator + "java" + File.separator;
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator;
        }
        return packagePath;
    }

    private static VelocityContext getVelocityContext(GeneratorParams generatorParams, TableEntity tableEntity) {
        Configuration config = getConfig();
        Map<String, Object> map = new HashMap<>();
        map.put("tableName", tableEntity.getTableName());
        map.put("comments", tableEntity.getComments());
        map.put("pk", tableEntity.getPk());
        map.put("className", tableEntity.getUpperClassName());
        map.put("classname", tableEntity.getLowerClassName());
        map.put("pathName", tableEntity.getLowerClassName().toLowerCase());
        map.put("columns", tableEntity.getColumns());
        map.put("package", generatorParams.getPackageName());
        map.put("moduleName", generatorParams.getModuleName());
        map.put("author", Config.AUTHOR);
        map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_PATTERN));
        VelocityContext context = new VelocityContext(map);
        return context;
    }

    private static void initVelocity() {
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);
    }

    /**
     * 表、字段参数处理
     *
     * @param generatorParams
     * @return
     */
    private static TableEntity formatParams(GeneratorParams generatorParams) {
        TableEntity tableEntity = new TableEntity();
        //表信息
        setTableEntity(tableEntity, generatorParams);
        //设置列信息
        setColumns(tableEntity, generatorParams);
        //没主键，则第一个字段为主键
        if (tableEntity.getPk() == null) {
            tableEntity.setPk(tableEntity.getColumns().get(0));
        }
        return tableEntity;
    }

    private static void setColumns(TableEntity tableEntity, GeneratorParams generatorParams) {
        List<ColumnEntity> columnsList = new ArrayList<>();
        for (DatabaseColumn column : generatorParams.getColumns()) {
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setColumnName(column.getColumnName());
            //列名转换成Java属性名
            String attrName = columnToJava(column.getColumnName());
            columnEntity.setUpperAttrName(attrName);
            columnEntity.setLowerAttrName(StringUtils.uncapitalize(attrName));
            columnEntity.setComments(column.getColumnComment());

            //列的数据类型，转换成Java类型
            Configuration config = getConfig();
            String attrType = config.getString(column.getColumnType(), "unknowType");
            columnEntity.setAttrType(attrType);
            //是否主键
            if (column.isPrimary()) {
                tableEntity.setPk(columnEntity);
            }
            columnsList.add(columnEntity);
        }
        tableEntity.setColumns(columnsList);
    }

    private static void setTableEntity(TableEntity tableEntity, GeneratorParams generatorParams) {
        tableEntity.setTableName(generatorParams.getTableName());
        tableEntity.setComments(generatorParams.getTableComment());
        //表名转换成Java类名
        Configuration config = getConfig();
        String className = tableToJava(tableEntity.getTableName(), config.getString("tablePrefix"));
        tableEntity.setUpperClassName(className);
        tableEntity.setLowerClassName(StringUtils.uncapitalize(className));
    }

    /**
     * 列名转换成Java属性名
     */
    private static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    /**
     * 表名转换成Java类名
     */
    private static String tableToJava(String tableName, String tablePrefix) {
        if (StringUtils.isNotBlank(tablePrefix)) {
            tableName = tableName.replaceFirst(tablePrefix, "");
        }
        return columnToJava(tableName);
    }

    /**
     * 获取配置信息
     */
    private static Configuration getConfig() {
        try {
            return new PropertiesConfiguration("generator.properties");
        } catch (ConfigurationException e) {
            throw new RuntimeException("获取配置文件失败，", e);
        }
    }

    /**
     * 获取文件名
     */
    private static String getFileName(String templateName, String className, String classname, GeneratorParams generatorParams) {
        String packagePath = getPackagePath(generatorParams);
        if (StringUtils.isNotBlank(templateName)) {
            String afterClassName = templateName.substring(templateName.lastIndexOf("/") + 1, templateName.indexOf("."));
            if (templateName.contains("template/Entity.java.vm" )) {
                return packagePath + afterClassName.toLowerCase() + File.separator + className + ".java";
            }else if (templateName.contains("template/UpdateDto.java.vm" )) {
                return packagePath + "dto" + File.separator + classname + File.separator + className + "UpdateDto.java";
            }else if (templateName.contains("template/CreateDto.java.vm" )) {
                return packagePath + "dto" + File.separator + classname + File.separator + className + "CreateDto.java";
            }else if (templateName.contains("template/PageDto.java.vm" )) {
                return packagePath + "dto" + File.separator + classname + File.separator + className + "PageDto.java";
            }else if (templateName.contains("template/Vo.java.vm")) {
                return packagePath + "vo" + File.separator + className + "Vo.java";
            }else if (templateName.contains("index.vue")) {
                return "vue" + File.separator + className + File.separator + "index.vue";
            }else if (templateName.contains("edit.vue")) {
                return "vue" + File.separator + className + File.separator + "edit.vue";
            }
            return packagePath + afterClassName.toLowerCase() + File.separator + className + afterClassName + ".java";
        }
        return null;
    }
}
